/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.utils;

import com.chasekernan.hxnova.utils.Utils;

/**
    Serializes and unserializes objects to an xml representation.
**/
class XmlSerializer{

    /**
        Serializes an object to xml.
        Will return null if the given object is not an object or it's null.
    **/
    public static function serializeObject(name : String, obj : Dynamic) : Xml {
        /*if(obj == null) return null;
        
        var xml = createXmlElement(name);
        
        switch(Type.typeof(obj)) {
            
            case TObject:
                
                for (f in Reflect.fields(obj)) {
                    serializeField(xml, obj, f);
                }
                
            case TClass(c):
                for (f in Reflect.fields(obj)) {
                    serializeField(xml, obj, f);
                }

            default:
                return null;
                
        }
        
        return xml;*/
        return null;
    }
    
    private static inline function createXmlElement(name : String) : Xml {
        return Xml.parse("<" + name + "></" + name + ">").firstElement();
    }
    
    /*private static function serialize(baseXml : Xml, obj : Dynamic, 
                                      name : String) {
        var childXml = createXmlElement(name);
        childXml.set("type", switch(Type.typeof(obj)) {
            
            case TNull:
                //do nothing
                return;
                
            case TInt:
                childXml.firstChild().nodeValue = Std.string(obj);
                "int";
        
            case TFloat:
                childXml.firstChild().nodeValue = Std.string(obj);
                "float";
                
            case TBool:
                childXml.firstChild().nodeValue = if(obj) "true" else "false";
                "bool";
        
            case TClass(c):
                switch(c) {
                    case untyped String:
                        childXml.firstChild().nodeValue = StringTools.urlEncode(
                                obj);
                        "string";
                    
                    case untyped Array:
                        for(i in 0...obj.length) {
                            var dummy = createXmlElement("dummy");
                            serialize(dummy, untyped obj[i], "value");
                            var valueXml = dummy.firstElement();
                            valueXml.set("index", Std.string(i));
                            childXml.addChild(valueXml);
                        }
                        "array";
                        
                    case cast List:
                        var list = cast(obj, List<Dynamic>);
                        for(value in list) {
                            serialize(childXml, value, "value");
                        }
                        "list";
                        
                    case cast Date:
                        childXml.firstChild().nodeValue =
                                    StringTools.urlEncode(obj.toString());
                        "date";
                        
                    case Hash:
                        var hash = cast(obj, Hash<Dynamic>);
                        for(key in hash.keys()) {
                            var dummy = createXmlElement("dummy");
                            serialize(dummy, hash.get(key), "value");
                            
                            var valueXml = dummy.firstElement();
                            valueXml.set("key", StringTools.urlEncode(key));
                            childXml.addChild(valueXml);
                        }
                        "hash";
    
                    case IntHash:
                        var intHash = cast(obj, IntHash<Dynamic>);
                        for(key in intHash) {
                            var dummy = createXmlElement("dummy");
                            serialize(dummy, intHash.get(key), "value");
                            
                            var valueXml = dummy.firstElement();
                            valueXml.set("key", StringTools.urlEncode(
                                    Std.string(key)));
                            childXml.addChild(valueXml);
                        }
                        "intHash";
                        
                    default:
                        //if the class has an id field, simply refernce it by
                        //id, instead of the full class definition.
                        if(Reflect.hasField(obj, "id")) {
                            childXml.firstChild().nodeValue =
                                        Std.string(Reflect.field(obj, "id"));
                        } else {
                        
                            for(f in Reflect.fields(obj)) {
                                serializeField(childXml, obj, f);
                            }
                        }
                        Type.getClassName(Type.getClass(obj));
                }

            case TObject:
                childXml = serializeObject(name, obj);
                "object";
                    
            case TEnum(e):
                childXml.set("name", Type.enumConstructor(untyped obj));
                childXml.set("enumType", Type.getEnumName(e));
                
                var params = Type.enumParameters(untyped obj);
                for(i in 0...params.length) {        
                    var dummy = createXmlElement("dummy");
                    serialize(dummy, params[i], "parameter");
                                
                    var paramXml = dummy.firstElement();
                    paramXml.set("index", Std.string(i));
                    
                    childXml.addChild(paramXml);
                }
                "enum";
                    
            default:
                throw "Cannot serialize: " + obj;
            
        });
        
        baseXml.addChild(childXml);
    }
                                          
        
    
    private static function serializeField(baseXml : Xml, obj : Dynamic, 
                                           field : String) {
        var f = Reflect.field(obj, field);
        serialize(baseXml, f, field);
    }
    
    public static function unserializeObject(xml : Xml, obj : Dynamic) {
        if (obj == null || xml == null) return;
        
        switch(Type.typeof(obj)) {
            
            case TObject:
                for(f in Reflect.fields(obj)) unserializeField(xml,obj, f);
                
            case TClass(c):
                for(f in Reflect.fields(obj)) {
                    unserializeField(xml, obj, f);
                }

            default:
                return;
                
        }
    }
    
    private static function unserializeField(baseXml : Xml, obj : Dynamic, 
                                            field : String) {
        var f = Reflect.field(obj, field);
        
        if(!Utils.xmlHasElement(baseXml, field)) {
            Reflect.setField(obj, field, null);
            return;
        }
        
        var xml = Utils.getXmlElement(baseXml, field);
        Reflect.setField(obj, field, unserialize(xml));
    }
    
    private static function unserialize(baseXml : Xml) : Dynamic {
        var firstChild = baseXml.firstChild();
        var nodeValue = firstChild.nodeValue;
        
        return untyped switch(baseXml.get("type")) {
                
            case "int":
                Std.parseInt(nodeValue);
        
            case "float":
                Std.parseFloat(nodeValue);
                
            case "bool":
                nodeValue == "true";
        
            case "string":
                StringTools.urlDecode(nodeValue);
                    
            case "array":
                var array = new Array<Dynamic>();
                
                for(valueXml in baseXml.elementsNamed("value")) {
                    var index = Std.parseInt(valueXml.get("index"));
                            
                    if (array.length <= index) {
                        for (i in array.length...(index - array.length + 1)) {
                            untyped array.push(null);
                        }
                    }
                    array[index] = unserialize(valueXml);
                }
                array;
                        
            case "list":
                var list = new List<Dynamic>();
                        
                for (valueXml in baseXml.elementsNamed("value")) {
                    list.push(unserialize(valueXml));
                }
                list;
                        
            case "date":
                Date.fromString(nodeValue);
                        
            case "hash":
                var hash = new Hash<Dynamic>();
                
                for(valueXml in baseXml.elementsNamed("value")) {
                    var key = valueXml.get("key");
                    hash.set(key, unserialize(valueXml));
                }
                hash;
                
            case "intHash":
                var intHash = new IntHash<Dynamic>();
                for(valueXml in baseXml.elementsNamed("value")) {
                    var key = Std.parseInt(valueXml.get("key"));
                    intHash.set(key, unserialize(valueXml));
                }
                intHash;

            case "object":
                var obj = {};
                for(fieldXml in baseXml.elements()) {
                    Reflect.setField(obj, fieldXml.nodeName, 
                                     unserialize(fieldXml));
                }
                obj;
                    
            case "enum":
                var enumType = Type.resolveEnum(baseXml.get("enumType"));
                var constructor = Reflect.field(enumType, baseXml.get("name"));
                var args = new Array<Dynamic>();
                
                for(paramXml in baseXml.elementsNamed("parameter")) {
                    var index = Std.parseInt(paramXml.get("index"));
                    
                    if (args.length <= index) {
                        for (i in args.length...(index - args.length + 1)) {
                            args.push(null);
                        }
                    }
                    args[index] = unserialize(paramXml);
                }
                
                if (args.length != 0) {
                    return Reflect.callMethod(enumType, constructor, args);
                }
                constructor;
                    
            default:
                var type = Type.resolveClass(baseXml.get("type"));
                
                try {
                    var id = Std.parseInt(baseXml.firstChild().nodeValue);
                    
                    try {
                        return type.getById(id);
                    } catch (e : Dynamic) {
                        throw "Instance is specified by id, but class " + type +
                              "has no function [getById].";
                    }
                } catch(e : Dynamic) {
                    var instance = Type.createEmptyInstance(type);
                    for(fieldXml in baseXml.elements()) {
                        Reflect.setField(instance, fieldXml.nodeName, 
                                     unserialize(fieldXml));
                    }
                    return instance;
                }
                //this is needed to satisfy the compiler.
                null;
        };
    }*/
        
}